package net.sansi.v3correctserver.service.correct.bright;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.sansi.v3correctserver.entity.IntPoint;
import net.sansi.v3correctserver.entity.image.RecImage;
import net.sansi.v3correctserver.entity.photorec.LedNode;
import net.sansi.v3correctserver.service.correct.RectCorrectBase;
import net.sansi.v3correctserver.service.photorec.rec.RecRectScreen;
import net.sansi.v3correctserver.util.Matrix;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Fang
 * create time : 2024/12/31
 * IDE : IntelliJ IDEA
 * <p>
 * 绘制 曲线矫正
 */

@Slf4j
@Getter
public class RectBrightCorrectCurve extends RectCorrectBase {


    // 参数
    private RectBrightCorrectCurve.Params params;

    public RectBrightCorrectCurve(RectBrightCorrectCurve.Params params) {
        this.params = params;
        initBaseParams(
                params.getResult().getWidth(),
                params.getResult().getHeight(),
                params.getSpan()
        );
    }

    double targetBright;
    int colorIdx;
    List<RecRectScreen> recs = new ArrayList<>();
    List<List<double[][]>> curves = List.of(new ArrayList<>(), new ArrayList<>(), new ArrayList<>());

    /**
     * 外部矫正入口
     *
     * @param ledPhoto led相片
     * @param colorIdx 颜色
     * @param fence    围栏
     * @return 矫正结果
     */
    public RectBrightCorrectCurve toCorrect(RecImage ledPhoto, int colorIdx, List<IntPoint> fence) {
        int photoCount = params.getLedPhotos().length;
        this.colorIdx = colorIdx;
        for (RecImage photo : params.getLedPhotos()) {
            recs.add(new RecRectScreen(photo, null, fence).toRec());
        }
        targetBright = getTargetBright();
        initCurve();
        RecRectScreen rec = ledPhoto == params.getLedPhotos()[photoCount - 1] ? recs.get(photoCount - 1) :
                new RecRectScreen(ledPhoto, null, fence).toRec();
        rec.getMatrix().foreach((x, y, led) -> {
            IntPoint intPoint = mxyToRxy(x, y);
            double scale = 1.0;
            if (led != null) {
                scale = computeResult(led.getBrightness(), searchCurve(led.getBrightness()));
            }
            params.result.get(intPoint.getX(), intPoint.getY())[colorIdx] = scale;
        });
        return this;
    }

    /**
     * 计算结果
     *
     * @param targetBright 目标亮度
     * @param curve        曲线
     * @return 结果
     */
    private double computeResult(double targetBright, double[][] curve) {
        int size = curve.length;
        if (targetBright >= curve[size - 1][0]) return 1.0;
        if (targetBright <= curve[0][0]) return curve[0][1] / curve[size - 1][1];
        for (int i = 1; i < size; i++) {
            double sb = curve[i - 1][0], eb = curve[i][0];
            double sw = curve[i - 1][1], ew = curve[i][1];
            if (targetBright >= eb) continue;
            double k = (ew - sw) / (eb - sb);
            return (sw + (targetBright - sb) * k) / curve[size - 1][1];
        }
        return 1.0;
    }

    /**
     * 搜索曲线
     * 二分法
     *
     * @param brightness 目标亮度
     * @return 曲线
     */
    private double[][] searchCurve(double brightness) {
        int size = recs.size();
        int left = 0, right = curves.size() - 1, res = 0;
        while (left <= right) {
            int mid = (left + right) >> 1;
            double mv = curves.get(colorIdx).get(mid)[size - 1][0];
            res = mid;
            if (mv > brightness) {
                right = mid - 1;
            } else if (mv < brightness) {
                left = mid + 1;
            } else break;
        }
        return curves.get(colorIdx).get(res);
    }

    /**
     * 初始化变化曲线
     */
    private void initCurve() {
        if (curves.get(colorIdx) != null && !curves.get(colorIdx).isEmpty()) return;
        int size = recs.size();
        recs.get(size - 1).getMatrix().foreach((x, y, led) -> {
            double[][] curve = new double[recs.size()][2];
            for (int i = 0; i < curve.length; i++) {
                RecRectScreen rec = recs.get(i);
                LedNode node = rec.getMatrix().get(x, y);
                if (node == null) return;
                curve[i][0] = node.getBrightness();
                curve[i][1] = params.getShowBrights()[i];
            }
            curves.get(colorIdx).add(curve);
        });
        // smooth
        curves.get(colorIdx).sort((a, b) -> (int) (a[size - 1][0] - b[size - 1][0]));
        for (int i = 0; i < curves.size() - 1; i++) {
            double[][] curve = curves.get(colorIdx).get(i);
            double[] sum = new double[size];
            int count = 0;
            for (int j = i - 20; j < i + 20; j++) {
                if (j < 0 || j >= curves.size()) continue;
                for (int k = 0; k < size; k++) {
                    sum[k] += curves.get(colorIdx).get(j)[k][0];
                }
                count++;
            }
            for (int j = 0; j < size; j++) {
                curve[j][0] = sum[j] / count;
            }
        }
    }

    /**
     * 目标亮度
     *
     * @return 衰减后的目标亮度
     */
    private double getTargetBright() {
        LedNode led = recs.get(recs.size() - 1).getLedByBrightSort(0.5);
        LedNode led2 = recs.get(recs.size() - 1).getLedByBrightSort(0.01);
        double decay = led.getBrightness() * params.getBrightnessDecay();
        double res = Math.max(led.getBrightness() * params.getBrightnessDecay(), led2.getBrightness());
        log.info("[目标相片亮度] {}, [衰减亮度] {} [原始亮度(中)] {}", res, decay, led.getBrightness());
        return res;
    }

    @Getter
    @Setter
    public static class Params {
        // 隔点
        int span;
        // led图片，打屏亮度由低到高
        RecImage[] ledPhotos;
        // 对应打屏亮度
        int[] showBrights;
        // 围栏
        Matrix<double[]> result;
        // 允许亮度衰减百分比
        double brightnessDecay = 0.2;
    }
}
